/*  klondike.c - the main source code for the game
 */

/*
    Klondike, a Patience Game for the Psion Series 3
    Version 1.0a
    Copyright (C) 1993  J Cade Roux

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

 * See the file COPYING for the GNU General Public License.
 * Mail: Cade Roux
 *			c/o Dubroca
 *			Box 513
 *			Boutte, LA  70039
 *			USA
 */

#include <plib.h>
#include <wlib.h>
#include <hwif.h>

#include <utils.h>

#include "global.h"			/* global definitions such as the version */
#include "klondike.h"		/* resource ids                           */

/* Parameters of deck/game */
#define N_SUITS			4
#define N_RANKS			13
#define N_CARDS			52
#define TABLEAU_SIZE		7

/* Spacing between cards */
#define GFX_HORZ_CARD_SPACING		6
#define GFX_VERT_CARD_SPACING		2

/* Card graphics dimensions */
#define GFX_CARD_WID					18
#define GFX_CARD_HGT					24

#define GFX_RANK_WID			6
#define GFX_RANK_HGT			6

#define GFX_SUIT_S_WID		7
#define GFX_SUIT_S_HGT		7
#define GFX_SUIT_L_WID		13
#define GFX_SUIT_L_HGT		13

#define GFX_BORDER_HGT		2
#define GFX_PARTIAL_HGT		10

#define GFX_ARR_WID			4
#define GFX_ARR_HGT			4

/* Offsets of design on card */
#define GFX_RANK_TOP			2
#define GFX_RANK_LEFT		2

#define GFX_SUIT_S_TOP		2
#define GFX_SUIT_S_LEFT		9
#define GFX_SUIT_L_TOP		9
#define GFX_SUIT_L_LEFT		3

/* Offsets in graphics data bitmap */
#define ICON_X					0
#define ICON_Y					0

#define GFX_BACK_X			23
#define GFX_BACK_Y			0

#define GFX_RANK_X(rank)	(41 + (rank) * GFX_RANK_WID)
#define GFX_RANK_Y			0

#define GFX_ARR_UP_X			41
#define GFX_ARR_UP_Y			19

#define GFX_SUIT_L_X(suit)		(41 + (suit) * GFX_SUIT_L_WID)
#define GFX_SUIT_S_X(suit)		(93 + (suit) * GFX_SUIT_S_WID)
#define GFX_SUIT_Y				6

/* Offsets of cards on screen */
#define GFX_HAND_X				6
#define GFX_HAND_Y				0

#define GFX_RUBB_X				(GFX_HAND_X + GFX_CARD_WID + GFX_HORZ_CARD_SPACING)
#define GFX_RUBB_Y				GFX_HAND_Y

#define GFX_SUIT_RED_Y			(GFX_HAND_Y + GFX_CARD_HGT + GFX_VERT_CARD_SPACING)
#define GFX_SUIT_BLACK_Y		(GFX_SUIT_RED_Y + GFX_CARD_HGT + GFX_VERT_CARD_SPACING)

enum {	SUIT_HEARTS = 0,
			SUIT_DIAMONDS,
			SUIT_CLUBS,
			SUIT_SPADES } ;

#define GFX_ACE_X(suit)		( ((suit) == SUIT_DIAMONDS || (suit) == SUIT_SPADES) ? GFX_RUBB_X : GFX_HAND_X )
#define GFX_ACE_Y(suit)		( ((suit) == SUIT_HEARTS || (suit) == SUIT_DIAMONDS) ? GFX_SUIT_RED_Y : GFX_SUIT_BLACK_Y )

#define GFX_PILE_X(pile)		(78 + (pile) * (GFX_CARD_WID + GFX_HORZ_CARD_SPACING))
#define GFX_PILE_Y				0

/* Two formats:
	index = suit * N_RANKS + rank
	rank = index % N_RANKS
	suit = index / N_RANKS

	Rank and Suit:		Suit: 0, 1, 2, 3: Hearts, Diamonds, Clubs, Spades resp.
							Rank: 0-12: Ace-King resp.

	Index:				0-51:		Ace-King Hearts, Ace-King Diamonds,
										Ace-King Clubs, Ace-King Spades
*/

/* The definition of a card */
typedef struct CARD_TAG {
	struct CARD_TAG *		pnext ;	/* ptr to next card                      */
	BYTE						index ;	/* high bit indicates if it's been dealt */
	BYTE						fill ;
} CARD ;

/* Masks for cards */
#define DEALT_MASK					0x80			/* high bit   */
#define INDEX_MASK					0x7F			/* low 7 bits */
#define DEALT(card)					((card.index & DEALT_MASK) != 0)
#define INDEX(pcard)					(pcard->index & INDEX_MASK)

/* Do cards *pcard1 and *pcard2 alternate */
#define ALTERN(pcard1,pcard2)		(	(INDEX(pcard1) > 25 || INDEX(pcard2) > 25) &&\
												(INDEX(pcard1) < 26 || INDEX(pcard2) < 26) )
#define RANK(pcard)					(INDEX(pcard) % N_RANKS)
#define SUIT(pcard)					(INDEX(pcard) / N_RANKS)

/*		Tableau pile:

		|----------|
		|XXXXXXXXXX|
		|----------|
		|XXXXXXXXXX|
		|----------|
		|XXXXXXXXXX| <-pdown
		|----------|
		| 10       | <-pup_top
		|----------|
		| 9        |
		|----------|
		| 8        |
		|----------|
		| 7        | <- pup_bot
		|          |
		|          |
		|          |
		|          |
		|          |
		|----------|
 */

typedef struct TABLEAU_TAG {
	CARD *	pdown ;
	CARD *	pup_top ;
	CARD *	pup_bot ;
	INT		num_down ;
	INT		num_up ;
} TABLEAU ;

LOCAL_D H_MENU_DATA		mdata[] = {	"Klondike", 2,
												NULL } ;

LOCAL_D TEXT *				cmds[] = {	"nNew Game",
												"xExit",
												NULL } ;

GLREF_D WSERV_SPEC *		wserv_channel ;
GLREF_D TEXT *				DatCommandPtr ;
GLDEF_D TEXT **			_cmds = cmds ;
GLDEF_D H_MENU_DATA *	_mdata = mdata ;

/* Cards in tableau etc. */
LOCAL_D struct {
	CARD *		deck ;
	TABLEAU		pile[TABLEAU_SIZE] ;
	CARD *		aces[N_SUITS] ;
	CARD *		hand ;
	CARD *		rubb ;
	CARD *		hold ;
} cards ;

#define PILE(n)	(cards.pile[n])
#define CPILE		(PILE(status.cpile))
#define LPILE		(PILE(status.lpile))

enum {	POS_HAND = 7,
			POS_RUBB,
			POS_HEARTS,
			POS_DIAMONDS,
			POS_CLUBS,
			POS_SPADES } ;

#define HOLDING			(cards.hold != NULL)
#define ON_TABLEAU		(status.cpile < TABLEAU_SIZE && status.cpile >= 0)
#define ON_HAND			(status.cpile == POS_HAND)
#define ON_RUBB			(status.cpile == POS_RUBB)
#define ON_SUITS			(status.cpile >= POS_HEARTS && status.cpile <= POS_SPADES)
#define EMPTY_HAND		(cards.hand == NULL)
#define EMPTY_RUBB		(cards.rubb == NULL)
#define EMPTY_SUIT		(cards.aces[status.cpile - POS_HEARTS] == NULL)
#define EMPTY_PILE		(CPILE.num_up == 0)
#define NO_PILE			100

LOCAL_D struct {
	ULONG			seedl ;			/* random number seed               */
	BOOL			quit ;			/* play requested quit              */
	BOOL			new_game ;		/* player requested a new game      */
	BOOL			turn_up ;		/* did a card nee to be turned up?  */
	INT			cx ;				/* current xpos of cursor           */
	INT			cy ;				/* current ypos of cursor           */
	INT			cpile ;			/* current pile                     */
	INT			lpile ;			/* pile of last play                */
	INT			num_dealt ;		/* number of cards dealt            */
} status = { 0L, FALSE, FALSE } ;

LOCAL_D struct {
	UINT  win ;						/* main window id                   */
	INT   gc ;						/* permanent gc for main window     */
	UINT	ptr ;						/* pointer window id                */
	UINT	bmp ;						/* gfx bitmap id                    */
} gfx ;

LOCAL_D P_RECT		RECT_CLR = { {0, 0}, {GFX_CARD_WID, 10000} } ;
LOCAL_D P_RECT		RECT_RANK = { {0, 0}, {GFX_RANK_WID, GFX_RANK_HGT} } ;
LOCAL_D P_RECT		RECT_SML = { {0, 0}, {GFX_SUIT_S_WID, GFX_SUIT_S_HGT} } ;
LOCAL_D P_RECT		RECT_LRG = { {0, 0}, {GFX_SUIT_L_WID, GFX_SUIT_L_HGT} } ;
LOCAL_D P_RECT		RECT_CARD = { {0, 0}, {GFX_CARD_WID, GFX_CARD_HGT} } ;
LOCAL_D P_RECT		RECT_BACK = { {GFX_BACK_X, GFX_BACK_Y}, {GFX_BACK_X + GFX_CARD_WID, GFX_BACK_Y + GFX_CARD_HGT} } ;
LOCAL_D P_RECT		RECT_DOWN = { {GFX_BACK_X, GFX_BACK_Y}, {GFX_BACK_X + GFX_CARD_WID, GFX_BACK_Y + GFX_BORDER_HGT} } ;
LOCAL_D P_RECT		RECT_BORDER = { {0, 0}, {GFX_CARD_WID, GFX_BORDER_HGT + 1} } ;
LOCAL_D P_RECT		RECT_PARTIAL = { {0, 0}, {GFX_CARD_WID, GFX_PARTIAL_HGT + 1} } ;
LOCAL_D P_RECT		RECT_PTR = { {GFX_ARR_UP_X, GFX_ARR_UP_Y}, {GFX_ARR_UP_X + GFX_ARR_WID, GFX_ARR_UP_Y + GFX_ARR_HGT} } ;

/* Deal a single card */
LOCAL_C INT DealCard(VOID) {
	INT	iLp = -1 ;
	INT	count = 0 ;
	INT	index ;

	/* Deal one of the remaining slots, and count down to that undealt card */
	for ( index = Random(&status.seedl, 0, N_CARDS - status.num_dealt - 1) ;
			count <= index ;
			count += !DEALT(cards.deck[++iLp]) ) ;

	/* Mark it dealt */
	cards.deck[iLp].index |= DEALT_MASK ;
	status.num_dealt++ ;
	return(iLp) ;
}

/* Clear a card-sized rectangle at x, y */
LOCAL_C VOID ClrCard(INT x, INT y) {
	P_RECT	rect = RECT_CARD ;

	p_offrec(&rect, x, y) ;
	gClrRect(&rect, G_TRMODE_CLR) ;
}

/* Draw Rank and Suit of *pcard whose tl is at x, y */
LOCAL_C VOID DrawRankandSuit(BOOL large, CARD *pcard, INT x, INT y) {
	P_RECT	rect ;
	P_POINT	pos ;

	rect = RECT_RANK ;
	pos.x = x + GFX_RANK_LEFT ;
	pos.y = y + GFX_RANK_TOP ;
	p_offrec(&rect, GFX_RANK_X(RANK(pcard)), GFX_RANK_Y) ;
	gCopyBit(&pos, gfx.bmp, &rect, G_TRMODE_SET) ;

	rect = large ? RECT_LRG : RECT_SML ;
	pos.x = x + (large ? GFX_SUIT_L_LEFT : GFX_SUIT_S_LEFT) ;
	pos.y = y + (large ? GFX_SUIT_L_TOP : GFX_SUIT_S_TOP) ;
	p_offrec(	&rect,
					large ? GFX_SUIT_L_X(SUIT(pcard)) : GFX_SUIT_S_X(SUIT(pcard)),
					GFX_SUIT_Y ) ;
	gCopyBit(&pos, gfx.bmp, &rect, G_TRMODE_SET) ;
}

/* Draw Card Outline at x, y */
LOCAL_C VOID DrawCardOutline(INT x, INT y) {
	P_RECT	rect ;

	rect = RECT_CARD ;
	p_offrec(&rect, x, y) ;
	gDrawBox(&rect) ;
}

/* Draw card *pcard at x, y */
LOCAL_C VOID DrawCard(CARD *pcard, INT x, INT y) {
	if ( pcard == NULL )
		return ;

	/* Draw Whole Face Up Card */
	DrawCardOutline(x, y) ;

	/* Draw Rank and Large Suit */
	DrawRankandSuit(TRUE, pcard, x, y) ;
}

/* Draw a card back at x, y */
LOCAL_C VOID DrawBack(INT x, INT y) {
	P_POINT 	pos ;

	pos.x = x ;
	pos.y = y ;
	gCopyBit(&pos, gfx.bmp, &RECT_BACK, G_TRMODE_SET) ;
}

/* Draw a tableau pile */
LOCAL_C VOID DrawTableau(INT pile) {
	P_POINT	pos ;
	P_RECT	rect = RECT_CLR ;
	INT		iLp ;

	/* Clear rectangle */
	p_offrec(&rect, GFX_PILE_X(pile), 0) ;
	gClrRect(&rect, G_TRMODE_CLR) ;

	/* Draw Face Down Borders */
	for ( pos.x = GFX_PILE_X(pile), pos.y = GFX_PILE_Y, iLp = 0 ;
			iLp < PILE(pile).num_down ;
			pos.y += GFX_BORDER_HGT, iLp++ )
		gCopyBit(&pos, gfx.bmp, &RECT_DOWN, G_TRMODE_SET) ;

	if ( PILE(pile).num_up > 1 ) {
		/* Draw Partial Face Up Card */
		rect = RECT_PARTIAL ;
		p_offrec(&rect, pos.x, pos.y) ;
		gDrawBox(&rect) ;

		/* Draw Rank and Small Suit */
		DrawRankandSuit(FALSE, PILE(pile).pup_top, pos.x, pos.y) ;

		/* Draw Face Up Borders */
		pos.y += GFX_PARTIAL_HGT ;
		rect = RECT_BORDER ;
		p_offrec(&rect, pos.x, pos.y) ;
		for ( iLp = 0 ;
				iLp < PILE(pile).num_up - 2 ;
				pos.y += GFX_BORDER_HGT, iLp++ ) {
			gDrawBox(&rect) ;
			p_offrec(&rect, 0, GFX_BORDER_HGT) ;
		}
	}

	if ( PILE(pile).num_up >= 1 )
		DrawCard(PILE(pile).pup_bot, pos.x, pos.y) ;
	else if ( PILE(pile).num_down > 0 )
		DrawBack(pos.x, pos.y - GFX_BORDER_HGT) ;
}

/* Draw the suit pile for suit */
LOCAL_C VOID DrawSuit(INT suit) {
	ClrCard(GFX_ACE_X(suit), GFX_ACE_Y(suit)) ;
	if ( cards.aces[suit] == NULL )
		DrawCardOutline(GFX_ACE_X(suit), GFX_ACE_Y(suit)) ;
	else
		DrawCard(cards.aces[suit], GFX_ACE_X(suit), GFX_ACE_Y(suit)) ;
}

/* Draw pile */
LOCAL_C VOID DrawPile(INT pile) {
	if ( pile < TABLEAU_SIZE )
		DrawTableau(pile) ;
	else switch (pile) {

		case POS_HAND :
			ClrCard(GFX_HAND_X, GFX_HAND_Y) ;
			if ( EMPTY_HAND )
				DrawCardOutline(GFX_HAND_X, GFX_HAND_Y) ;
			else
				DrawBack(GFX_HAND_X, GFX_HAND_Y) ;
			break ;

		case POS_RUBB :
			ClrCard(GFX_RUBB_X, GFX_RUBB_Y) ;
			if ( EMPTY_RUBB )
				DrawCardOutline(GFX_RUBB_X, GFX_RUBB_Y) ;
			else
				DrawCard(cards.rubb, GFX_RUBB_X, GFX_RUBB_Y) ;
			break ;

		default :
			DrawSuit(pile - POS_HEARTS) ;
	}
}

/* Return card to pile it was taken from */
LOCAL_C VOID CancelPlay(VOID) {

	if (	status.lpile >= 0 &&
			status.lpile < TABLEAU_SIZE ) {

		if ( LPILE.num_up == 0 ) {
			INT		iLp ;
			CARD *	pcard ;

			LPILE.pup_top = cards.hold ;
			cards.hold = NULL ;

			for (	iLp = 1, pcard = LPILE.pup_top ;
					pcard->pnext != NULL ;
					pcard = pcard->pnext, iLp++ ) ;

			LPILE.pup_bot = pcard ;
			LPILE.num_up = iLp ;
		}
		else {
			LPILE.pup_bot->pnext = cards.hold ;
			cards.hold = NULL ;
			LPILE.pup_bot = LPILE.pup_bot->pnext ;
			LPILE.num_up++ ;
		}

	}
	else if ( status.lpile == POS_RUBB ) {
		cards.hold->pnext = cards.rubb ;
		cards.rubb = cards.hold ;
		cards.hold = NULL ;
	}
	else if (	status.lpile >= POS_HEARTS &&
					status.lpile <= POS_SPADES ) {
		cards.hold->pnext = cards.aces[status.lpile - POS_HEARTS] ;
		cards.aces[status.lpile - POS_HEARTS] = cards.hold ;
		cards.hold = NULL ;
	}
	DrawPile(status.lpile) ;
	status.lpile = NO_PILE ;
	status.turn_up = FALSE ;
}

/* Update pile position from x, y coords */
LOCAL_C VOID UpdatePos(VOID) {
	if ( status.cx < 2 )
		switch(status.cy) {
			case 0 :
				status.cpile = ( status.cx ? POS_RUBB : POS_HAND ) ;
				break ;
			case 1 :
				status.cpile = ( !status.cx ? POS_HEARTS : POS_DIAMONDS ) ;
				break ;
			case 2 :
				status.cpile = ( !status.cx ? POS_CLUBS : POS_SPADES ) ;
				break ;
		}
	else {
		status.cpile = status.cx - 2 ;
		status.cy = ( (CPILE.num_up < 2) ? 0 : (status.cy > 0) ) ;
	}
}

/* Turn up a face down card left exposed by a lift */
LOCAL_C VOID TurnUpCard(VOID) {
	if ( !status.turn_up )
		return ;

	if ( LPILE.num_down == 0 ) {
		/* No face down cards */
		LPILE.pup_top = LPILE.pup_bot = NULL ;
		LPILE.num_up = 0 ;
	}
	else {
		/* At least one face down card */
		LPILE.pup_bot = LPILE.pup_top = LPILE.pdown ;
		LPILE.pdown = LPILE.pdown->pnext ;
		LPILE.pup_bot->pnext = NULL ;
		LPILE.num_up = 1 ;
		LPILE.num_down-- ;
	}

	DrawPile(status.lpile) ;
	status.lpile = NO_PILE ;
	status.turn_up = FALSE ;
}

/* Draw 3 cards or recycle hand */
LOCAL_C VOID HandDraw(VOID) {
	if ( EMPTY_HAND ) {
		CARD *	handt ;
		CARD *	rubbt ;

		/* Recycle hand */
		for ( ; !EMPTY_RUBB ; ) {
			rubbt = cards.rubb->pnext ;
			handt = cards.hand ;
			cards.hand = cards.rubb ;
			cards.hand->pnext = handt ;
			cards.rubb = rubbt ;
		}
	}
	else {
		INT			iLp ;
		CARD *		pcard ;

		/* Deal three cards */
		for ( iLp = 0 ; iLp < 3 && !EMPTY_HAND ; iLp++ ) {
			pcard = cards.hand->pnext ;
			cards.hand->pnext = cards.rubb ;
			cards.rubb = cards.hand ;
			cards.hand = pcard ;
		}
	}
	DrawPile(POS_RUBB) ;
}

/* Pick up cards from the current pile */
LOCAL_C VOID PickUpCards(VOID) {
	CARD *	pcard ;

	if ( HOLDING )
		return ;

	status.lpile = NO_PILE ;
	status.turn_up = FALSE ;

	if (	ON_TABLEAU &&
			CPILE.pup_top != NULL ) {
		if (	status.cy == 0 ||
				CPILE.num_up == 1 ) {
			cards.hold = CPILE.pup_top ;
			CPILE.pup_top = CPILE.pup_bot = NULL ;
			CPILE.num_up = 0 ;
			status.lpile = status.cpile ;
			status.turn_up = ( CPILE.num_down != 0 ) ;
		}
		else {
			cards.hold = CPILE.pup_bot ;
			for (	pcard = CPILE.pup_top ;
					pcard->pnext != cards.hold ;
					pcard = pcard->pnext ) ;
			pcard->pnext = NULL ;
			CPILE.pup_bot = pcard ;
			CPILE.num_up-- ;
			status.lpile = status.cpile ;
		}
	}
	else switch (status.cpile) {

		case POS_HAND :
			HandDraw() ;
			break ;

		case POS_RUBB :
			if ( !EMPTY_RUBB ) {
				/* Pick up top rubbish card */
				cards.hold = cards.rubb ;
				cards.rubb = cards.rubb->pnext ;
				cards.hold->pnext = NULL ;
				UpdatePos() ;
				status.lpile = status.cpile ;
			}
			break ;

		case POS_HEARTS :
		case POS_DIAMONDS :
		case POS_CLUBS :
		case POS_SPADES :
			if ( !EMPTY_SUIT ) {
				cards.hold = cards.aces[status.cpile - POS_HEARTS] ;
				cards.aces[status.cpile - POS_HEARTS] = cards.hold->pnext ;
				cards.hold->pnext = NULL ;
				status.lpile = status.cpile ;
			}
			break ;

		default :
			break ;
	}
}

/* Drop currently held card on the suits piles */
LOCAL_C VOID PutOnSuits(VOID) {
	if (	HOLDING &&
			cards.hold->pnext == NULL ) {
		INT		suit ;

		suit = SUIT(cards.hold) ;
		if (	RANK(cards.hold) == 0 ||
				( cards.aces[suit] != NULL &&
				RANK(cards.hold) == RANK(cards.aces[suit]) + 1 ) ) {
			cards.hold->pnext = cards.aces[suit] ;
			cards.aces[suit] = cards.hold ;
			cards.hold = NULL ;
			DrawPile(POS_HEARTS + suit) ;
			TurnUpCard() ;
		}
	}
}

/* Drop currently held card on the current pile */
LOCAL_C VOID PutDownCards(VOID) {
	CARD *	pcard ;
	INT		iLp ;

	if ( !HOLDING )
		return ;

	if (	ON_TABLEAU &&
			CPILE.num_up != 0 &&
			RANK(cards.hold) == RANK(CPILE.pup_bot) - 1 &&
			ALTERN(cards.hold, CPILE.pup_bot) ) {
		CPILE.pup_bot->pnext = cards.hold ;
		cards.hold = NULL ;
		for (	iLp = 1, pcard = CPILE.pup_top ;
				pcard->pnext != NULL ;
				pcard = pcard->pnext, iLp++ ) ;
		CPILE.pup_bot = pcard ;
		CPILE.num_up = iLp ;
		TurnUpCard() ;
	}
	else if (	ON_TABLEAU &&
					CPILE.num_down == 0 &&
					CPILE.num_up == 0 &&
					RANK(cards.hold) == 12 ) {
		CPILE.pup_top = cards.hold ;
		cards.hold = NULL ;
		for (	iLp = 1, pcard = CPILE.pup_top ;
				pcard->pnext != NULL ;
				pcard = pcard->pnext, iLp++ ) ;
		CPILE.pup_bot = pcard ;
		CPILE.num_up = iLp ;
		TurnUpCard() ;
	}
	else switch (status.cpile) {

		case POS_HEARTS :
		case POS_DIAMONDS :
		case POS_CLUBS :
		case POS_SPADES :
			PutOnSuits() ;
			break ;

		default :
			break ;
	}
}

/* Execute menu command */
LOCAL_C VOID ExecuteCommand(INT keycode) {
	if ( (keycode = uLocateCommand(keycode)) < 0 )
		return ;

	switch(keycode) {

		case 0 : /* New Game */
			Check( p_enter5(Query, RS_NEW_GAME, RS_YES, RS_NO, &status.new_game) ) ;
			break ;

		case 1 : /* Exit */
			Check( p_enter5(Query, RS_QUIT, RS_YES, RS_NO, &status.quit) ) ;
			break ;

		default :
			break ;
	}
}

/* Modify the pointer to take account of the game status */
LOCAL_C VOID UpdatePointer(BOOL drawflag) {
	W_WINDATA	windata ;
	P_POINT		pos = { 0, 0 } ;

	switch (status.cpile) {

		case POS_HAND :
			windata.extent.tl.x = GFX_HAND_X ;
			windata.extent.tl.y = GFX_HAND_Y ;
			break ;

		case POS_RUBB :
			windata.extent.tl.x = GFX_RUBB_X ;
			windata.extent.tl.y = GFX_RUBB_Y ;
			break ;

		case POS_HEARTS :
		case POS_DIAMONDS :
		case POS_CLUBS :
		case POS_SPADES :
			windata.extent.tl.x = GFX_ACE_X(status.cpile - POS_HEARTS) ;
			windata.extent.tl.y = GFX_ACE_Y(status.cpile - POS_HEARTS) ;
			break ;

		default :
			if ( ON_TABLEAU ) {
				windata.extent.tl.x = GFX_PILE_X(status.cpile) ;
				windata.extent.tl.y = GFX_PILE_Y + CPILE.num_down * GFX_BORDER_HGT ;
				if (	((!HOLDING && status.cy > 0) || HOLDING) &&
						CPILE.num_up > 1 )
					windata.extent.tl.y += GFX_PARTIAL_HGT + (CPILE.num_up - 2) * GFX_BORDER_HGT ;
			}
			break ;
	}

	windata.extent.tl.x -= (!HOLDING ? 5 : 6) ;
	windata.extent.tl.y += (!HOLDING ? 2 : 9) ;
	windata.extent.width = (!HOLDING ? GFX_ARR_WID : GFX_CARD_WID) ;
	windata.extent.height = (!HOLDING ? GFX_ARR_HGT : GFX_CARD_HGT) ;

	wSetWindow(gfx.ptr, W_WIN_EXTENT, &windata) ;
	if ( drawflag ) {
		gCreateTempGC0(gfx.ptr) ;
		if ( HOLDING ) {
			ClrCard(0, 0) ;
			DrawCard(cards.hold, 0, 0) ;
		}
		else
			gCopyBit(&pos, gfx.bmp, &RECT_PTR, G_TRMODE_REPL) ;
		gFreeTempGC() ;
	}
}

#pragma save, ENTER_CALL

/* Deal a new game */
LOCAL_C INT Deal(VOID) {
	INT		iLp ;
	INT		jLp ;
	CARD *	pcard ;

	status.cpile = POS_HAND ;
	status.cx = status.cy = 0 ;
	status.seedl = p_date() ;
	status.num_dealt = 0 ;

	cards.hand = cards.rubb = cards.hold = NULL ;
	for ( iLp = 0 ; iLp < N_SUITS ; cards.aces[iLp++] = NULL ) ;

	/* Clear Deck */
	for ( iLp = 0 ; iLp < N_CARDS ; iLp++ ) {
		cards.deck[iLp].pnext = NULL ;
		cards.deck[iLp].index = iLp ;
	}

	/* Deal cards to tableau */
	for ( iLp = 0 ; iLp < TABLEAU_SIZE ; iLp++ ) {

		PILE(iLp).pdown = NULL ;

		/* Deal face down cards */
		for ( jLp = 1 ; jLp <= iLp ; jLp++ ) {
			pcard = PILE(iLp).pdown ;
			PILE(iLp).pdown = &cards.deck[DealCard()] ;
			PILE(iLp).pdown->pnext = pcard ;
		}

		/* Deal face up card */
		PILE(iLp).pup_top = &cards.deck[DealCard()] ;
		PILE(iLp).pup_bot = PILE(iLp).pup_top ;
		PILE(iLp).pup_top->pnext = PILE(iLp).pup_bot->pnext = NULL ;
		PILE(iLp).num_down = iLp ;
		PILE(iLp).num_up = 1 ;

	}

	/* Deal remaining cards to hand */
	for ( ; status.num_dealt < N_CARDS ; ) {
		pcard = cards.hand ;
		cards.hand = &cards.deck[DealCard()] ;
		cards.hand->pnext = pcard ;
	}

	for ( iLp = 0 ; iLp <= POS_SPADES ; DrawPile(iLp++) ) ;

	UpdatePointer(TRUE) ;

	return(E_APP_NONE) ;
}

/* Play a single game until the player quits, or a new game is requested */
LOCAL_C INT PlayGame(VOID) {
	WMSG_KEY		key ;
	INT			ret ;
	BOOL			ud_pos = FALSE ;

	status.new_game = FALSE ;

	do {

		uGetKey(&key) ;

		if ( key.keycode == CONS_EVENT_COMMAND )
			status.quit = TRUE ;
		else if ( key.keycode & W_SPECIAL_KEY )
			ExecuteCommand(key.keycode & ~W_SPECIAL_KEY) ;
		else switch( key.keycode ) {

			case W_KEY_HELP :
				if ( key.modifiers == W_CTRL_MODIFIER )
					SystemHelp() ;
				else
					Help(RH_MAIN, RH_MAIN) ;
				break ;

			case W_KEY_MENU :
				if ( (ret = uPresentMenus()) > 0 )
					ExecuteCommand(ret) ;
				break ;

			case W_KEY_UP :
				if ( status.cy > 0 )
					status.cy-- ;
				break ;

			case W_KEY_DOWN :
				if ( status.cy < 2 )
					status.cy++ ;
				break ;

			case W_KEY_RIGHT :
				if ( status.cx < 8 )
					status.cx++ ;
				break ;

			case W_KEY_LEFT :
				if ( status.cx > 0 )
					status.cx-- ;
				break ;

			case W_KEY_PAGE_UP :
				status.cy = 0 ;
				break ;

			case W_KEY_PAGE_DOWN :
				status.cy = 2 ;
				break ;

			case W_KEY_HOME :
				status.cx = 0 ;
				break ;

			case W_KEY_END :
				status.cx = 8 ;
				break ;

			case W_KEY_RETURN :
				if ( !HOLDING ) {
					HandDraw() ;
					DrawPile(POS_HAND) ;
				}
				break ;

			case W_KEY_TAB :
				if ( HOLDING ) {
					PutOnSuits() ;
					PutDownCards() ;
				}
				else
					PickUpCards() ;
				DrawPile(status.cpile) ;
				UpdatePointer(TRUE) ;
				break ;

			case ' ' :
				if ( HOLDING )
					PutDownCards() ;
				else
					PickUpCards() ;
				DrawPile(status.cpile) ;
				UpdatePointer(TRUE) ;
				break ;

			case W_KEY_ESCAPE :
				if (	HOLDING &&
						status.lpile != NO_PILE ) {
					CancelPlay() ;
					UpdatePointer(TRUE) ;
				}
				break ;

			default :
				break ;
		}

			

		if (	ud_pos ||
				( key.keycode >= W_KEY_UP && key.keycode <= W_KEY_END ) ) {
			ud_pos = FALSE ;
			UpdatePos() ;
			UpdatePointer(FALSE) ;
		}

	} while ( !(status.new_game || status.quit) ) ;

	return(E_APP_NONE) ;
}

/* General initialization of global variables etc. */
LOCAL_C INT SpecificInit(VOID) {
	P_POINT		bit_size ;
	W_WINDATA	windata = { W_WIN_NO_REDRAW,
									{ {0, 0}, GFX_ARR_WID, GFX_ARR_HGT },
									W_WIN_NO_MOUSE,
									W_WIN_BACK_BITMAP } ;

	hCrackCommandLine() ;
	p_unmarka() ;

	OpenResource(DatCommandPtr) ;

	/* Gfx init */
	gfx.win = uFindMainWid() ;
	gfx.bmp = gOpenBit(DatCommandPtr, 0, 0, &bit_size) ;
	gfx.gc = gCreateGC0(gfx.win) ;
	gfx.ptr = wCreateWindow(	gfx.win,
										W_WIN_NO_REDRAW | W_WIN_EXTENT | W_WIN_BACKGROUND,
										&windata, (UWORD) &gfx) ;
	wInitialiseWindowTree(gfx.ptr) ;
	wMakeVisible(gfx.ptr) ;

	/* Mem alloc */
	cards.deck = f_alloc(N_CARDS * sizeof(CARD)) ;
	
	/* About message */
	Help(RH_ABOUT, RH_MAIN) ;

	return(E_APP_NONE) ;
}

#pragma restore

GLDEF_C VOID main(VOID) {
	uCommonInit() ;
	wDisableLeaves(FALSE) ;
	Check( p_enter1(SpecificInit) ) ;
	do {
		Check( p_enter1(Deal) ) ;
		Check( p_enter1(PlayGame) ) ;
	} while ( !status.quit ) ;
	p_exit(E_APP_NONE) ;
}

